﻿//////////////////////////////////////////////////////////////////////////////////
//
// ActionScript 3.0 Common Library (MIT License)
// Copyright (c) 2009 AS3CommonCode | Tony Birleffi | http://www.mosaiceye.com
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////////

package org.as3commoncode.net
{
	import ArgumentError;
	import flash.display.Loader;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestHeader;
	import flash.utils.getTimer;
	
	/**
	 * Note: utility class from examino moved into different namespace for 
	 * portability originally com.examino.net.
	 *
	 * @see 	   flash.net.URLLoader;
	 * @author     Matthew at exanimo.com
 	 * @author     Ryan Sprake
	 */
	public class BandwidthChecker extends URLLoader
	{	
		/** * The bytes per second. */
		public static const BYTES_PER_SECOND:String = 'bytesPerSecond';
		
		/** * The bits per second. */
		public static const BITS_PER_SECOND:String = 'bitsPerSecond';
		
		/** * The kilobytes per second. */
		public static const KILOBYTES_PER_SECOND:String = 'kilobytesPerSecond';
		
		/** * The kilobits per second. */
		public static const KILOBITS_PER_SECOND:String = 'kilobitsPerSecond';	
	
		/** * The maximum bytes. */
		public var maximumBytes:uint;
			
		/** * The units. */
		private var _units:String;
		
		/** * The bytes. */
		private var _bytes:Number;
		
		/** * The start time. */
		private var _startTime:Number;
		
		/** * The download time. */
		private var _downloadTime:Number;
		
		/**
		 * Get the bandwidth.
		 * 
		 * @return Number.
		 */
		public function get bandwidth():Number
		{
			switch(this.units)
			{
				case BYTES_PER_SECOND:
					return this._bytes / this._downloadTime * 1000;
				case BITS_PER_SECOND:
					return this._bytes * 8 / this._downloadTime * 1000;
				case KILOBITS_PER_SECOND:
					return this._bytes * 8 / this._downloadTime;
				case KILOBYTES_PER_SECOND:
				default:
					return this._bytes / this._downloadTime;
			}
		}

		/**
		 * Creates a bandwidth-checking URLLoader.
		 * 
		 * @param requestOrURL    the URLRequest or url (String) identifying the file to use.
		 */
		public function BandwidthChecker(requestOrURL:* = null)
		{
			this.units = KILOBYTES_PER_SECOND;
			this.addEventListener(ProgressEvent.PROGRESS, this._calulateBandwidth);
			this.addEventListener(Event.COMPLETE, this._calulateBandwidth);
			if (requestOrURL) this.check(requestOrURL);
		}
		
		/**
		 * Estimate the user's bandwidth by downloading a file (or part of a file).
		 *
		 * @param requestOrURL    the URLRequest or url (String) identifying the file to use.
		 */
		public function check(requestOrURL:*):void
		{
			if (requestOrURL is String)
			{
				var request:URLRequest = new URLRequest(requestOrURL);
				this.load(request);
			}
			else if (requestOrURL is URLRequest)
			{
				this.load(requestOrURL);
			}
			else
			{
				throw new ArgumentError('Argument must be either a url or URLRequest.');
			}
		}
		
		/**
		 * Perform the check. Overridden so that request can be manipulated to
		 * prevent caching and to start timer.
		 *
		 * @param request    a url request
		 */
		public override function load(request:URLRequest):void
		{
			// Mess with the URLRequest to make sure we don't load a cached version.
			var oldURL:String = request.url;
			if (/^(http:|https:)/i.test(request.url) || ((request.url.indexOf('://') == -1) && /^(http:|https:)/i.test(new Loader().contentLoaderInfo.loaderURL)))
			{
				var sep:String = request.url.indexOf('?') == -1 ? '?' : '&';
				request.url = request.url + sep + 'cacheBuster=' + new Date().getTime().toString();
			}
			
			var noCacheHeader:URLRequestHeader = new URLRequestHeader('pragma', 'no-cache');
			request.requestHeaders.push(noCacheHeader);
			
			// Load it.
			this._startTime = getTimer();
			super.load(request);
			
			// Put the request back to its original state.
			request.url = oldURL;
			for (var i:Number = 0; i < request.requestHeaders.length; i++)
			{
				if (request.requestHeaders[i] == noCacheHeader)
				{
					request.requestHeaders.splice(i, 1);
					break;
				}
			}
		}
		
		/**
		 * A helper function that updates the bandwidth reading whenever a chunk of
		 * the file is downloaded. If the file has been downloaded (or maximumBytes
		 * has been reached), a COMPLETE event will be dispatched. At this point,
		 * you can use the bandwidth property to get the user's estimated bandwidth.
		 * 
		 * @param e		Event.
		 */
		private function _calulateBandwidth(e:Event):void
		{
			this._downloadTime = getTimer() - this._startTime;
			this._bytes = e.currentTarget['bytesLoaded'];

			switch (e.type)
			{
				case Event.COMPLETE:
					this.removeEventListener(ProgressEvent.PROGRESS, this._calulateBandwidth);
					this.removeEventListener(Event.COMPLETE, this._calulateBandwidth);
				break;
				
				case ProgressEvent.PROGRESS:
					if (this.maximumBytes && (this._bytes >= this.maximumBytes))
					{
						this.removeEventListener(ProgressEvent.PROGRESS, this._calulateBandwidth);
						this.removeEventListener(Event.COMPLETE, this._calulateBandwidth);
						this.close();
	
						e = new Event(Event.COMPLETE);
						this.dispatchEvent(e);
					}
				break;
			}
		}
		
		/**
		 * Get the units.
		 * 
		 * @return String.
		 */
		public function get units():String
		{
			return this._units;
		}
		
		/**
		 * Set the units.
		 * 
		 * @param units		String.
		 */
		public function set units(units:String):void
		{
			switch(units)
			{
				case BYTES_PER_SECOND:
				case BITS_PER_SECOND:
				case KILOBYTES_PER_SECOND:
				case KILOBITS_PER_SECOND:
					this._units = units;
				break;
				default:
					throw new Error('Invalid value. Valid values for this property are BandwidthChecker.BYTES_PER_SECOND, BandwidthChecker.BITS_PER_SECOND, BandwidthChecker.KILOBYTES_PER_SECOND, and BandwidthChecker.KILOBITS_PER_SECOND.');
			}
		}
	}
}